Description
Leagues clutter the tab bar when out of season. Auto-hide a league when it has no games within ~-30/+30 days of now (its last/next game), surfacing it only when active. Special-case rare events: World Cup, Olympics, etc. recur every ~4 years and should stay hidden until in window. This is distinct from TASK-002's manual enable/disable: manual choice should still override (user can force-show or force-hide). Likely a derived 'in season' flag on the effective league set, computed from the fetched window / a schedule probe, applied in App.leagues resolution.
Acceptance Criteria
- #1 Leagues with no games within ~+/-30 days auto-hide from tabs and all views
- #2 Quadrennial events (World Cup, Olympics) stay hidden until they are in window
- #3 Manual enable/disable (TASK-002) still overrides the auto behavior
- #4 An auto-hidden league reappears automatically once it has games in range
Implementation Notes
Reworking auto-vs-manual flip into a per-league override model: auto-by-season always on; config carries force-hide + force-show sets + a display order. Effective visible = (inSeason OR forceShow) AND NOT forceHide. Toggling back to auto's choice clears the override (returns that league to auto, doesn't pin it). Hiding one league no longer disables auto-hide for the rest.
Refactored to per-league overrides (no global auto/manual flip). visible(l)=(inSeason OR forceShow) AND NOT forceHide. config now stores LeagueOrder + HideLeagues + ShowLeagues. toggleLeagueVisibility clears the override when the desired state matches the season default, so hiding ONE league no longer disables auto-hide for the rest; '0' clears all overrides. Tests rewritten for the new API (orderedCatalog, applyAutoLeagues, overrides-beat-season, toggle-clears-to-auto, moveLeague). go test ./... passes.
Final Summary
Seasonal auto-hide: off-season leagues drop out of the tabs and all views automatically, returning when in range.
What:
- model.League gains SeasonDays + SeasonWindow() (default 30, World Cup/quadrennial 90).
- App.autoMode (true when the user hasn't customized leagues) derives App.leagues from App.inSeason via applyAutoLeagues (catalog order): a league shows only if it has games within its ±SeasonWindow. Unprobed leagues count as in-season (startup shows all, then narrows — never flashes out/back); all-off-season falls back to the full catalog so the app is never blank.
- In-season status comes from a separate wide scan (seasonScan/fetchSeason: a ±SeasonWindow scoreboard probe per catalog league) at startup + a slow 10-min ticker, independent of the narrow display fetch. A scan error treats the league as in-season (transient failure never hides it).
- Manual override (TASK-002): editing leagues in settings flips autoMode off and persists, but only on an ACTUAL change (settingsDirty) so opening settings doesn't lock auto. '0' in settings resets to auto and restarts the scan; manual mode skips scanning. Settings header shows the current mode.
Verified live (June 2026): NFL probes 0 games in +/-30d -> auto-hidden; MLB/WNBA/World Cup/NBA/NHL in season. Unit tests cover the in-season derivation (catalog order, unprobed=in-season) and the never-blank fallback. go vet + go test ./... pass.